﻿//------------------------------------------------------------------------------
//  Namespace: FruitVentDesignDemo.Util
//  
//  Function： N/A
//  Name： IdWorker
//  
//  Ver       Time                     Author
//  0.10      2021/6/23 15:25:03      FruitVent
//
//  此代码版权归作者本人FruitVent所有
//  源代码使用协议遵循本仓库的开源协议及附加协议，若本仓库没有设置，则按MIT开源协议授权
//  CSDN博客：https://blog.csdn.net/weixin_39552347
//  源代码仓库：https://gitee.com/fruitvent
//  感谢您的下载和使用
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;

namespace FruitVentDesignDemo.Util
{
    public class IdWorker
    {
        private readonly long _workerId;
        private readonly long _datacenterId;
        private long _sequence = 0L;

        private static readonly long _twepoch = 1288834974657L;

        private static readonly long _workerIdBits = 5L;
        private static readonly long _datacenterIdBits = 5L;
        private static readonly long _maxWorkerId = -1L ^ (-1L << (int)_workerIdBits);
        private static readonly long _maxDatacenterId = -1L ^ (-1L << (int)_datacenterIdBits);
        private static readonly long _sequenceBits = 12L;

        private readonly long _workerIdShift = _sequenceBits;
        private readonly long _datacenterIdShift = _sequenceBits + _workerIdBits;
        private readonly long _timestampLeftShift = _sequenceBits + _workerIdBits + _datacenterIdBits;
        private readonly long _sequenceMask = -1L ^ (-1L << (int)_sequenceBits);

        private long _lastTimestamp = -1L;
        private static readonly object _syncRoot = new object();

        public IdWorker(long workerId, long datacenterId)
        {

            // sanity check for workerId
            if (workerId > _maxWorkerId || workerId < 0)
            {
                throw new ArgumentException(string.Format("worker Id can't be greater than %d or less than 0", _maxWorkerId));
            }
            if (datacenterId > _maxDatacenterId || datacenterId < 0)
            {
                throw new ArgumentException(string.Format("datacenter Id can't be greater than %d or less than 0", _maxDatacenterId));
            }
            this._workerId = workerId;
            this._datacenterId = datacenterId;
        }

        public long NextId()
        {
            lock (_syncRoot)
            {
                long timestamp = TimeGen();

                if (timestamp < _lastTimestamp)
                {
                    throw new ApplicationException(string.Format("Clock moved backwards.  Refusing to generate id for %d milliseconds", _lastTimestamp - timestamp));
                }

                if (_lastTimestamp == timestamp)
                {
                    _sequence = (_sequence + 1) & _sequenceMask;
                    if (_sequence == 0)
                    {
                        timestamp = TilNextMillis(_lastTimestamp);
                    }
                }
                else
                {
                    _sequence = 0L;
                }

                _lastTimestamp = timestamp;

                return ((timestamp - _twepoch) << (int)_timestampLeftShift) | (_datacenterId << (int)_datacenterIdShift) | (_workerId << (int)_workerIdShift) | _sequence;
            }
        }

        protected long TilNextMillis(long lastTimestamp)
        {
            long timestamp = TimeGen();
            while (timestamp <= lastTimestamp)
            {
                timestamp = TimeGen();
            }
            return timestamp;
        }

        protected long TimeGen()
        {
            return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
        }
    }
}
